diff --git a/api/src/main/java/net/kyori/adventure/text/format/DecorationMap.java b/api/src/main/java/net/kyori/adventure/text/format/DecorationMap.java
index 81fa8ae2c..50ad2fb18 100644
--- a/api/src/main/java/net/kyori/adventure/text/format/DecorationMap.java
+++ b/api/src/main/java/net/kyori/adventure/text/format/DecorationMap.java
@@ -1,7 +1,7 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
- * Copyright (c) 2017-2021 KyoriPowered
+ * Copyright (c) 2017-2022 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -40,21 +40,57 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Unmodifiable;
+/**
+ * Bit-set driven decoration -> state map.
+ * Given that both {@link TextDecoration} and {@link TextDecoration.State} are enums, every value of each
+ * can be indexed by its ordinal. The way this works is by combining that property and "inserting" the state index
+ * as value in the text decoration index.
+ *
+ *
For each possible state value:
+ *
+ * - {@code State.NOT_SET -> 0b00}
+ * - {@code State.FALSE -> 0b01}
+ * - {@code State.TRUE -> 0b10}
+ *
+ *
+ * we assign one bit position to each decoration value ({@code p} representing the position holding the state value):
+ *
+ * - {@code TextDecoration.OBFUSCATED -> 0b0000p}
+ * - {@code TextDecoration.BOLD -> 0b000p0}
+ * - {@code TextDecoration.STRIKETHROUGH -> 0b00p00}
+ * - {@code TextDecoration.UNDERLINED -> 0b0p000}
+ * - {@code TextDecoration.ITALIC -> 0bp0000}
+ *
+ *
+ * but since State is tri-state it occupies two bits {@code [0b00, 0b01, 0b10]}, each decoration type must reserve
+ * two bits for itself, expanding 5 bits to 10 bits, two for each decoration type:
+ *
+ * - {@code TextDecoration.OBFUSCATED -> 0b00000000pp}
+ * - {@code TextDecoration.BOLD -> 0b000000pp00}
+ * - {@code TextDecoration.STRIKETHROUGH -> 0b0000pp0000}
+ * - {@code TextDecoration.UNDERLINED -> 0b00pp000000}
+ * - {@code TextDecoration.ITALIC -> 0bpp00000000}
+ *
+ *
+ * Here the {@code pp} bit pair represents the potential state values from the first list above.
+ *
+ * It is however possible to compact this even more using trinary logic and fit all of this into
+ * a single {@code byte}, I however am not doing that because it's more effort than my time's worth.
+ */
@Unmodifiable
final class DecorationMap extends AbstractMap implements Examinable {
private static final TextDecoration.State[] STATES = TextDecoration.State.values();
private static final int MAP_SIZE = StyleImpl.DECORATIONS.length;
private static final TextDecoration.State[] EMPTY_STATE_ARRAY = {};
- static final DecorationMap EMPTY = createEmptyDecorationMap();
+ static final DecorationMap EMPTY = new DecorationMap(0); // NOT_SET = 0 (happens to be the first State entry!)
// key set is universal, all decorations always exist in any given style
private static final KeySet KEY_SET = new KeySet();
static DecorationMap fromMap(final Map decorationMap) {
if (decorationMap instanceof DecorationMap) return (DecorationMap) decorationMap;
int bitSet = 0;
- for (int i = 0; i < MAP_SIZE; i++) {
- final TextDecoration decoration = StyleImpl.DECORATIONS[i];
+ for (final TextDecoration decoration : StyleImpl.DECORATIONS) {
bitSet |= decorationMap.getOrDefault(decoration, TextDecoration.State.NOT_SET).ordinal() * offset(decoration);
}
return withBitSet(bitSet);
@@ -62,28 +98,20 @@ static DecorationMap fromMap(final Map dec
static DecorationMap merge(final Map first, final Map second) {
int bitSet = 0;
- for (int i = 0; i < MAP_SIZE; i++) {
- final TextDecoration decoration = StyleImpl.DECORATIONS[i];
+ for (final TextDecoration decoration : StyleImpl.DECORATIONS) {
bitSet |= first.getOrDefault(decoration, second.getOrDefault(decoration, TextDecoration.State.NOT_SET)).ordinal() * offset(decoration);
}
return withBitSet(bitSet);
}
+ // TODO: use an array cache? bitset value = index of corresponding DecorationMap in the array
private static DecorationMap withBitSet(final int bitSet) {
- return bitSet == EMPTY.bitSet ? EMPTY : new DecorationMap(bitSet);
- }
-
- private static DecorationMap createEmptyDecorationMap() {
- int bitSet = 0;
- for (int i = 0; i < MAP_SIZE; i++) {
- bitSet |= TextDecoration.State.NOT_SET.ordinal() * offset(StyleImpl.DECORATIONS[i]);
- }
- return new DecorationMap(bitSet);
+ return bitSet == 0 ? EMPTY : new DecorationMap(bitSet);
}
private static int offset(final TextDecoration decoration) {
- // ordinal * 2, decoration states are tristate so they occupy two bits each [0b00, 0b01, 0b10]
- return 1 << decoration.ordinal() * 2;
+ // ordinal * 2, decoration states are tri-state so they occupy two bits each [0b00, 0b01, 0b10]
+ return 1 << (decoration.ordinal() * 2);
}
private final int bitSet;
@@ -100,10 +128,8 @@ private DecorationMap(final int bitSet) {
Objects.requireNonNull(state, "state");
Objects.requireNonNull(decoration, "decoration");
final int offset = offset(decoration);
- return withBitSet(
- this.bitSet & ~(0b11 * offset) // 'reset' the state bits
- | state.ordinal() * offset
- );
+ // 'reset' the state bits for the given decoration, and 'merge' the new state's bits
+ return withBitSet((this.bitSet & ~(0b11 * offset)) | (state.ordinal() * offset));
}
@Override
@@ -115,7 +141,7 @@ private DecorationMap(final int bitSet) {
@Override
public TextDecoration.State get(final Object o) {
if (o instanceof TextDecoration) {
- return STATES[this.bitSet >> ((TextDecoration) o).ordinal() * 2 & 0b11];
+ return STATES[(this.bitSet >> (((TextDecoration) o).ordinal() * 2)) & 0b11];
}
return null;
}
diff --git a/api/src/main/java/net/kyori/adventure/text/format/StyleImpl.java b/api/src/main/java/net/kyori/adventure/text/format/StyleImpl.java
index b245e8a45..17947eff9 100644
--- a/api/src/main/java/net/kyori/adventure/text/format/StyleImpl.java
+++ b/api/src/main/java/net/kyori/adventure/text/format/StyleImpl.java
@@ -23,6 +23,7 @@
*/
package net.kyori.adventure.text.format;
+import java.util.EnumMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -275,7 +276,8 @@ static final class BuilderImpl implements Builder {
@Override
public @NotNull Builder decoration(final @NotNull TextDecoration decoration, final TextDecoration.@NotNull State state) {
requireNonNull(state, "state");
- this.decorations.replace(decoration, state);
+ requireNonNull(decoration, "decoration");
+ this.decorations.put(decoration, state);
return this;
}