Skip to content

Commit

Permalink
feat(minimessage): use virtual components to preserve rainbow/gradien…
Browse files Browse the repository at this point in the history
…t tags
  • Loading branch information
zml2008 authored and kashike committed Apr 2, 2023
1 parent 2458518 commit 94367eb
Show file tree
Hide file tree
Showing 9 changed files with 798 additions and 576 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,13 @@ private MiniMessageSerializer() {
private static void visit(final @NotNull Component component, final Collector emitter, final SerializableResolver resolver, final boolean lastChild) {
// visit self
resolver.handle(component, emitter);
emitter.flushClaims(component);
Component childSource = emitter.flushClaims(component);
if (childSource == null) {
childSource = component;
}

// then children
for (final Iterator<Component> it = component.children().iterator(); it.hasNext();) {
for (final Iterator<Component> it = childSource.children().iterator(); it.hasNext();) {
emitter.mark();
visit(it.next(), emitter, resolver, lastChild && !it.hasNext());
}
Expand Down Expand Up @@ -336,9 +339,11 @@ public boolean styleClaimed(final @NotNull String claimId) {
return this.claimedStyleElements.contains(claimId);
}

void flushClaims(final Component component) {
@Nullable Component flushClaims(final Component component) { // return: a substitute to provide children
Component ret = null;
if (this.componentClaim != null) {
this.componentClaim.emit(this);
ret = this.componentClaim.substitute();
this.componentClaim = null;
} else if (component instanceof TextComponent) {
this.text(((TextComponent) component).content());
Expand All @@ -347,6 +352,7 @@ void flushClaims(final Component component) {
throw new IllegalStateException("Unclaimed component " + component);
}
this.claimedStyleElements.clear();
return ret;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
*/
package net.kyori.adventure.text.minimessage.internal.serializer;

import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* Something that holds data representable as MiniMessage tags.
Expand All @@ -39,4 +41,16 @@ public interface Emitable {
* @since 4.10.0
*/
void emit(final @NotNull TokenEmitter emitter);

/**
* Provide a substitute for this component's actual children.
*
* <p>This allows modifying tags to output original data while still transforming the created components.</p>
*
* @return a potential substitute
* @since 4.13.0
*/
default @Nullable Component substitute() { // TODO: maybe make this be only for component claims?
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,19 @@

import java.util.Collections;
import java.util.PrimitiveIterator;
import java.util.function.Consumer;
import java.util.stream.Stream;
import net.kyori.adventure.internal.Internals;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.VirtualComponent;
import net.kyori.adventure.text.VirtualComponentHolder;
import net.kyori.adventure.text.flattener.ComponentFlattener;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.minimessage.internal.parser.node.TagNode;
import net.kyori.adventure.text.minimessage.internal.parser.node.ValueNode;
import net.kyori.adventure.text.minimessage.internal.serializer.Emitable;
import net.kyori.adventure.text.minimessage.internal.serializer.TokenEmitter;
import net.kyori.adventure.text.minimessage.tag.Inserting;
import net.kyori.adventure.text.minimessage.tag.Modifying;
import net.kyori.adventure.text.minimessage.tree.Node;
Expand Down Expand Up @@ -95,25 +100,31 @@ public final void postVisit() {

@Override
public final Component apply(final @NotNull Component current, final int depth) {
if (depth == 0) {
// capture state into a virtual component, no other logic is needed in normal MM handling
return Component.virtual(new TagInfoHolder(new ComponentData(this.preserveData(), current)));
}

if ((this.disableApplyingColorDepth != -1 && depth > this.disableApplyingColorDepth) || current.style().color() != null) {
if (this.disableApplyingColorDepth == -1 || depth < this.disableApplyingColorDepth) {
this.disableApplyingColorDepth = depth;
}
// This component has its own color applied, which overrides ours
// We still want to keep track of where we are though if this is text
if (current instanceof TextComponent) {
final String content = ((TextComponent) current).content();
final int len = content.codePointCount(0, content.length());
for (int i = 0; i < len; i++) {
// increment our color index
this.advanceColor();
}
this.skipColorForLengthOf(((TextComponent) current).content());
}
return current.children(Collections.emptyList());
}

this.disableApplyingColorDepth = -1;
if (current instanceof TextComponent && ((TextComponent) current).content().length() > 0) {
if (current instanceof VirtualComponent) {
// this component has its own information, so we can't rainbowify direct content -- we can process children tho
// basically treat as if it's a non-text component
this.skipColorForLengthOf(((VirtualComponent) current).content());

return current.children(Collections.emptyList());
} else if (current instanceof TextComponent && ((TextComponent) current).content().length() > 0) {
final TextComponent textComponent = (TextComponent) current;
final String content = textComponent.content();

Expand All @@ -138,6 +149,14 @@ public final Component apply(final @NotNull Component current, final int depth)
return Component.empty().mergeStyle(current);
}

private void skipColorForLengthOf(final String content) {
final int len = content.codePointCount(0, content.length());
for (int i = 0; i < len; i++) {
// increment our color index
this.advanceColor();
}
}

// The lifecycle

protected abstract void init();
Expand All @@ -155,6 +174,14 @@ public final Component apply(final @NotNull Component current, final int depth)
*/
protected abstract TextColor color();

/**
* Return an emitable that will accurately reserialize the provided input data.
*
* @return the emitable for this tag
* @since 4.13.0
*/
protected abstract @NotNull Consumer<TokenEmitter> preserveData();

// misc

@Override
Expand All @@ -170,4 +197,55 @@ public final Component apply(final @NotNull Component current, final int depth)

@Override
public abstract int hashCode();

static final class ComponentData implements Emitable {
final Consumer<TokenEmitter> output;
final Component originalComp;

ComponentData(final Consumer<TokenEmitter> output, final Component originalComp) {
this.output = output;
this.originalComp = originalComp;
}

@Override
public void emit(final @NotNull TokenEmitter emitter) {
this.output.accept(emitter);
}

@Override
public Component substitute() {
return this.originalComp;
}
}

static final class TagInfoHolder implements VirtualComponentHolder<ComponentData> {
private final ComponentData data;

TagInfoHolder(final ComponentData data) {
this.data = data;
}

@Override
public @NotNull ComponentData unbox() {
return this.data;
}

@Override
public @NotNull String fallbackString() {
return ""; // only holds data for reserialization, not for display
}
}

static @Nullable Emitable claimComponent(final Component comp) {
if (!(comp instanceof VirtualComponent)) {
return null;
}

final VirtualComponentHolder<?> holder = ((VirtualComponent) comp).holder();
if (!(holder instanceof TagInfoHolder)) {
return null;
}

return ((TagInfoHolder) holder).unbox();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,13 @@
import java.util.List;
import java.util.Objects;
import java.util.OptionalDouble;
import java.util.function.Consumer;
import java.util.stream.Stream;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.internal.serializer.SerializableResolver;
import net.kyori.adventure.text.minimessage.internal.serializer.TokenEmitter;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
Expand All @@ -47,8 +51,10 @@
*/
final class GradientTag extends AbstractColorChangingTag {
private static final String GRADIENT = "gradient";
private static final TextColor DEFAULT_WHITE = TextColor.color(0xffffff);
private static final TextColor DEFAULT_BLACK = TextColor.color(0x000000);

static final TagResolver RESOLVER = TagResolver.resolver(GRADIENT, GradientTag::create);
static final TagResolver RESOLVER = SerializableResolver.claimingComponent(GRADIENT, GradientTag::create, AbstractColorChangingTag::claimComponent);

private int index = 0;
private int colorIndex = 0;
Expand Down Expand Up @@ -102,7 +108,7 @@ private GradientTag(final float phase, final List<TextColor> colors) {
}

if (colors.isEmpty()) {
this.colors = new TextColor[]{TextColor.color(0xffffff), TextColor.color(0x000000)};
this.colors = new TextColor[]{DEFAULT_WHITE, DEFAULT_BLACK};
} else {
this.colors = colors.toArray(new TextColor[0]);
}
Expand Down Expand Up @@ -145,6 +151,38 @@ protected TextColor color() {
}
}

@Override
protected @NotNull Consumer<TokenEmitter> preserveData() {
final TextColor[] colors;
final float phase;

if (this.negativePhase) {
colors = Arrays.copyOf(this.colors, this.colors.length);
Collections.reverse(Arrays.asList(colors));
phase = this.phase - 1;
} else {
colors = this.colors;
phase = this.phase;
}

return emit -> {
emit.tag(GRADIENT);
if (colors.length != 2 || !colors[0].equals(DEFAULT_WHITE) || !colors[1].equals(DEFAULT_BLACK)) { // non-default params
for (final TextColor color : colors) {
if (color instanceof NamedTextColor) {
emit.argument(NamedTextColor.NAMES.keyOrThrow((NamedTextColor) color));
} else {
emit.argument(color.asHexString());
}
}
}

if (phase != 0) {
emit.argument(Float.toString(phase));
}
};
}

@Override
public @NotNull Stream<? extends ExaminableProperty> examinableProperties() {
return Stream.of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@
package net.kyori.adventure.text.minimessage.tag.standard;

import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Stream;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.internal.serializer.SerializableResolver;
import net.kyori.adventure.text.minimessage.internal.serializer.TokenEmitter;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
Expand All @@ -44,7 +47,7 @@ final class RainbowTag extends AbstractColorChangingTag {
private static final String REVERSE = "!";
private static final String RAINBOW = "rainbow";

static final TagResolver RESOLVER = TagResolver.resolver(RAINBOW, RainbowTag::create);
static final TagResolver RESOLVER = SerializableResolver.claimingComponent(RAINBOW, RainbowTag::create, AbstractColorChangingTag::claimComponent);

private final boolean reversed;
private final int phase;
Expand Down Expand Up @@ -105,6 +108,22 @@ protected TextColor color() {
return TextColor.color(HSVLike.hsvLike(hue, 1f, 1f));
}

@Override
protected @NotNull Consumer<TokenEmitter> preserveData() {
final boolean reversed = this.reversed;
final int phase = this.phase;
return emit -> {
emit.tag(RAINBOW);
if (reversed && phase != 0) {
emit.argument(REVERSE + phase);
} else if (reversed) {
emit.argument(REVERSE);
} else if (phase != 0) {
emit.argument(Integer.toString(phase));
}
};
}

@Override
public @NotNull Stream<? extends ExaminableProperty> examinableProperties() {
return Stream.of(ExaminableProperty.of("phase", this.phase));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/
package net.kyori.adventure.text.minimessage;

import java.util.Arrays;
import java.util.Collections;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -75,4 +76,9 @@ public static Context dummyContext(final String originalMessage) {
public static ArgumentQueue emptyArgumentQueue(final Context context) {
return new ArgumentQueueImpl<>(context, Collections.<Tag.Argument>emptyList());
}

public static Component virtualOfChildren(final ComponentLike... children) {
return Component.virtual(() -> "") // not part of equality... should it be?
.children(Arrays.asList(children));
}
}
Loading

0 comments on commit 94367eb

Please sign in to comment.