Skip to content
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

WorldEdit 6.x #293

Merged
merged 107 commits into from
Apr 4, 2014
Merged

WorldEdit 6.x #293

merged 107 commits into from
Apr 4, 2014

Conversation

sk89q
Copy link
Member

@sk89q sk89q commented Apr 3, 2014

This changes the version of WorldEdit to 6.0.0-SNAPSHOT. I would like to merge this into master within the next 1-2 days unless something comes up.

Ideally, I would like to test various WorldEdit-using plugins to see if anything breaks that we can rectify on our end.

Performance and functionality are unaffected.

Goals

  • Rewrite most operations (//set, //replace) to be easily split up into several passes that can be executed over several ticks.
  • Make better use of Patterns, Masks, Regions and other long-available abstractions in WorldEdit.
  • Reorganize the packages in WorldEdit so that there are fewer top level packages.
  • Massively reduce the amount of code duplication present in EditSession.
  • Breakup complex logic (block reordering etc.) in EditSession.
  • Maintain or exceed performance of operations after the changes.
  • Better document WorldEdit code and add preconditions whenever possible.
  • Make it easier to implement support for entities (in addition to blocks) in all operations.
  • Replace EditSessionFactory with a better alternative that is forward-compatible and can support multiple plugins at a time.
  • Make use of Guava, which is now included on all platforms that WorldEdit is available on (Bukkit, Forge, vanilla Minecraft, Canary, MCEdu, etc.)

Challenges

  • High source and binary compatibility must be kept for third-party plugins that make use of WorldEdit.
  • Non-backwards compatible binary changes should not break important plugins such as block loggers due to LinkageErrors such as NoClassDefFoundError.

Changes

What's New

  • Introduced new Operation interface and related classes that supports executing tasks over several ticks.
  • Added new Extent interface that abstracts block operations (and in the future, entity operations) as they apply to worlds, clipboards, .schematic files, and so on.
  • Added new Pattern and Mask interfaces to replace the old ones, which are still in WorldEdit but are deprecated. The new classes use the new pattern and mask interfaces.
  • Added new 2D versions of patterns and masks.
  • Added new Transform interface for performing Vector transforms, with AffineTransform and Identity as implementations.
  • Added new ChangeSet and Change history objects that supports storing changes other than for blocks.
  • Added new EventBus class (borrowed from Guava, but with changes) for sending events internally within WorldEdit.
  • Added new NoiseGenerator for generating noise in 2D and 3D.
  • Added new internal.* package for internal classes.

What's Changed

  • Rewrote most calls in EditSession to use a functional approach that reduces code duplication.
  • Moved complex block setting logic from EditSession into a dozen different Extent implementations.
  • Removed EditSessionFactory. Added a new event bus to allow block loggers and other plugins to hook into block setting.
  • Reorganized, renamed, and removed lesser-used packages like snapshots.* and data.*
  • Removed foundation.* package.
  • Added new bukkit, bukkit-test and legacy source roots.

What's Broke

  • Block loggers and "async implementations" that currently hook into WorldEdit via an EditSessionFactory no longer can do so via that method, and may break due to a binary-incompatible change to EditSessionFactory (a change may be added to reduce this binary-incompatibility change, but the removal of EditSessionFactory will proceed).
  • Platform implementions of WorldEdit that are not maintained by us may break due to changes to packages. The notable change is that BlockBag was moved to a different package.
  • Plugins that use of the snapshot or chunk loading classes in WorldEdit (a number that is presumably very small) will break.

What Shouldn't Have Broken

  • Plugins that use WorldEdit's selections.
  • Plugins that use WorldEdit to load/save schematic files or copy and paste portions of worlds.

What's Left

  • Finish rewriting functions in EditSession to use the new visitor pattern.
  • Rewrite the clipboard and schematic loading code to be Extent-compatible.
  • Make WorldEdit actually split operations over several ticks.
  • Move the functions out of EditSession into a set of utility classes with static methods that create Operations for the various commands. (i.e. AnalyisCommands.count(Region, Mask) would return an Operation that can be run). All methods in EditSession would then be deprecated.
  • Do something about the change to block string IDs and deprecate BlockID and BlockType.
  • Add support for entities across the board.
  • Switch to a declarative command registration framework (i.e. the new-commands branch or something similar).
  • Maybe make WorldEdit a singleton, and then having platform implementations register their implementation on the singleton (i.e. WorldEdit.getInstance().registerImpl(bukkit)).
  • Add localication, support for calling commands from console, and so on...

Sample Code

Stacking a Region (//stack)

ForwardExtentCopy is a general purpose Extent to Extent copy operation that can be used for stacking, copying, pasting, and so on.

Vector size = region.getMaximumPoint().subtract(region.getMinimumPoint()).add(1, 1, 1);
Vector to = region.getMinimumPoint();
ForwardExtentCopy copy = new ForwardExtentCopy(this, region, this, to);
copy.setRepetitions(count);
copy.setTransform(new AffineTransform().translate(dir.multiply(size)));
Operations.completeLegacy(copy);

Adding a Block Logger

AbstractLoggingExtent is an abstract class that is intended for block loggers, as it will automatically simplify the logging operation to the act of overriding a onBlockChange method.

WorldEdit.getInstance().getEventBus().register(new Object() {
    @Subscribe
    public void wrapForLogging(EditSessionEvent event) {
        LocalPlayer player = event.getPlayer();

        event.setExtent(new AbstractLoggingExtent(event.getExtent()) {
            @Override
            protected void onBlockChange(Vector position, BaseBlock newBlock) {
                System.out.println(player + " set block @ " + position);
            }
        });
    }
});

sk89q added 30 commits March 27, 2014 08:22
We have to check to some serious breakage across the board before we can
release.
RegionMaskFilter handles it.
@DarkArc
Copy link
Member

DarkArc commented Apr 3, 2014

There should probably be a means to "cancel" an edit. That way a block protector could stop a block change without giving a block logger invalid data.

@sk89q
Copy link
Member Author

sk89q commented Apr 3, 2014

@DarkArc You can just implement a regular Extent rather than the AbstractLoggingExtent

WorldEdit.getInstance().getEventBus().register(new Object() {
    @Subscribe(priority = EventHandler.Priority.EARLY)
    public void wrapToDestroyEverything(EditSessionEvent event) {
        LocalPlayer player = event.getPlayer();

        event.setExtent(new ExtentDelegate(event.getExtent()) {
            @Override
            public boolean setBlock(Vector location, BaseBlock block) {
                throw new RuntimeException("No block for you");
            }
        });
    }
});

Or rather a ExtentDelegate which has all the methods implemented for the Extent interface so you can only override the ones that you care about.

@DarkArc
Copy link
Member

DarkArc commented Apr 4, 2014

@TomyLobo What does anything I'm saying have to do with the transaction system?

Here's how I understand the program at the moment.

Extents are chained more or less so it goes extent - > extent - > extent - > extent until it runs out of things to do. A block logger would use onBlockChange(); as would a block protector. However, they would have differing priorities.

I see no problem if the block protector immediately throws an exception stopping the chain. However, if a block protector throws an exception half way through the chain being evaluated, we have just set several blocks for an edit that was not fully completed, and will probably be undone by the user.

This means that for all the extents which have already been executed, extent will have made a block logger log the chain, and edited the world.

While it is not a necessary an issue, I think it would be better if we first checked to see if the transaction could be completed in full before we began editing anything, and before any block loggers received data.

That way we avoid the case where someone //paste, their edit is denied at the 1,000,000th out of 1.540,000 blocks which would as I understand the program trigger the logging of 1m edits, and 1m block changes. Then it would also be likely that this user is unsatisfied with the edit because 540,000 blocks were not placed. This will then leave us with the additional 1m edits and logs from the user reverting their mistake. totaling 2 million avoidable log records, and 2m blocks worth of CPU.

If that's not how this system works, please feel free to correct me. However, if it is, I think we should do something to prevent the above case while everything is being redesigned.

@sk89q
Copy link
Member Author

sk89q commented Apr 4, 2014

You are misunderstanding how this works. It works like an OutputStream.

How do you stop a byte from being written?

outputStream = new FileOutputStream(...);

outputStream = new OutputStream() {
    @Override
    public void write(int b) throws IOException {
    }
};

A block protector would not use onBlockChange. That method only exists if you use AbstractLoggingExtent, which explicitly is for logging.

sk89q added a commit that referenced this pull request Apr 4, 2014
Merge in WorldEdit 6.x branch -- contains breaking API changes
@sk89q sk89q merged commit 8badc52 into master Apr 4, 2014
@TomyLobo
Copy link
Collaborator

TomyLobo commented Apr 4, 2014

no slow reflection-based events for worldedit logging - good

@DarkArc
Copy link
Member

DarkArc commented Apr 4, 2014

Speaking of loggers, LogBlock/LogBlock#542, how's this look?

@sk89q
Copy link
Member Author

sk89q commented Apr 4, 2014

@DarkArc

  • Might want to not register the Extent if event.getPlayer() is null, since if it's null, it will stay null.
  • I'm thinking that they may want to support both the old WorldEdit and 6.x WorldEdit, especially since 6.x release is not imminent.

@sk89q
Copy link
Member Author

sk89q commented Apr 4, 2014

Also, one thing that may want to decide is whether we want to add Player (or Actor) interfaces first, so we can change all the API to take that. That way, LocalPlayer becomes an abstract implementation and we can work with an interface.

If we do this, we should do this ASAP.

wizjany referenced this pull request Apr 9, 2014
This breaks backwards compatibility for all getWorld() methods, but
shim methods were added for binary compatibility with method calls that
use LocalWorld.
@sk89q sk89q deleted the visitor branch June 29, 2014 05:19
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.

None yet

4 participants